<?php

namespace common\services;

use cm;
use ReflectionClass;
use ReflectionMethod;
use yii;

class Authorization extends yii\base\Model
{
    private $sysID = 1;

    //刷新权限库
    public function refresh($sysID)
    {
        $this->sysID = $sysID;
        $apps = $this->getApps();
        $this->updateApps($apps);
        Cm::$res->susess([], '更新成功');
    }

    //文档权限验证
    public function docPermissions()
    {
        return $this->checkDocPermissions();
    }

    public function rolePermissions($accountID)
    {
        return $this->checkRolePermissions($accountID);
    }

    //******************************************************************************************************************
    private function getApps()
    {
        $apps[] = ["alias" => "platform", "path" => "platform", "namespace" => "platform"];
        foreach (json_decode(APP_DIRS, true) as $dir) {
            $apps[] = ["alias" => $dir, "path" => "app/{$dir}", "namespace" => "app\\{$dir}"];
        }

        foreach ($apps as &$app)
            $this->getModules($app);

        return $apps;
    }

    private function getModules(&$app)
    {
        try {
            $reflection = new ReflectionClass ("{$app['namespace']}\Module");
            $classDoc = $reflection->getDocComment();
            $app['name'] = $this->getStrByReg($classDoc, "name");
            $app['remark'] = $this->getStrByReg($classDoc, "remark");
            $app['sort'] = $this->getStrByReg($classDoc, "sort", 99);
            $app['deprecated'] = $this->getStrBoolByReg($classDoc, 'deprecated');

            $this->getControllers($app);
        } catch (\ReflectionException $e) {
            Cm::$res->error($e->getMessage());
        }
    }

    private function getControllers(&$app)
    {
        $dir = $app['path'] . '/interfaces';
        $files = scandir($dir);
        $controllers = [];
        foreach ($files as $file) {
            if ($file != '.' && $file != '..') {
                if (is_dir($dir . '/' . $file)) {
                    $childFiles = scandir($dir . '/' . $file);
                    foreach ($childFiles as $cfile) {
                        if ($cfile != '.' && $cfile != '..') {
                            $controller = [
                                'alias' => $file . '/' . strtolower(str_replace('Interface.php', '', $cfile)),
                                'namespace' => $app['namespace'] . "\\interfaces\\{$file}\\" . str_replace('.php', '', $cfile),
                                'path' => $app['path'] . "/interfaces/{$file}",
                            ];
                            $this->getActions($controller);
                            $controllers[] = $controller;
                        }
                    }
                } else {
                    $controller = [
                        'alias' => strtolower(str_replace('Interface.php', '', $file)),
                        'namespace' => $app['namespace'] . "\\interfaces\\" . str_replace('.php', '', $file),
                        'path' => $app['path'] . "/interfaces"
                    ];

                    $this->getActions($controller);

                    $controllers[] = $controller;
                }
            }
        }

        $app['md5'] = md5(json_encode($controllers));
        $app['controllers'] = $controllers;
    }

    private function getActions(&$controller)
    {
        try {
            $reflection = new ReflectionClass ($controller['namespace']);
            $classDoc = $reflection->getDocComment();
            $controller['name'] = $this->getStrByReg($classDoc, "name");
            $controller['remark'] = $this->getStrByReg($classDoc, "remark");
            $controller['sort'] = $this->getStrByReg($classDoc, "sort", 99);
            $controller['md5'] = md5($classDoc);
            $controller['deprecated'] = $this->getStrBoolByReg($classDoc, 'deprecated');
            $controller['actions'] = [];

            $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
            foreach ($methods as $method) {
                $doc = $method->getDocComment();
                $controller['actions'][] = [
                    'name' => $this->getStrByReg($doc, "name"),
                    'alias' => str_replace('action', '', strtolower($method->name)),
                    'md5' => md5($doc),
                    'remark' => $this->getStrByReg($doc, "remark"),
                    'sort' => $this->getStrByReg($doc, 'sort', 99),
                    'deprecated' => $this->getStrBoolByReg($doc, 'deprecated'),
                    'type' => $this->getStrByReg($doc, 'type', 'page'),
                    'permissions' => $this->getPermissions($doc)
                ];
            }
        } catch (\ReflectionException $e) {
            Cm::$res->error($e->getMessage());
        }
    }

    private function getPermissions($doc)
    {
        $permissions = [
            'guest' => $this->getStrBoolByReg($doc, 'guest'),
            'common' => '',
            'access' => $this->getStrBoolByReg($doc, "access"),
            'get' => [],
            'post' => []
        ];

        if ($permissions['guest'])
            return $permissions;

        $common = $this->getStrBoolByReg($doc, 'common');//是否通用，不需要权限验证
        $strCommon = $this->getStrByReg($doc, 'common');
        $common = !$common ? 'no' : (empty($strCommon) ? 'all' : $strCommon);//no all get post
        $permissions['common'] = $common;
        if ($common == 'all')//不需要权限验证，所有权限
            return $permissions;

        if ($common == 'get' || $common == "no")//获取 post 权限；
        {
            $permissions['post'] = $this->getArrByReg($doc, 'post');
        }

        if ($common == 'post' || $common == "no")//获取 post 权限；
        {
            $permissions['get'] = $this->getArrByReg($doc, 'get');
        }

        return $permissions;
    }

    //******************************************************************************************************************
    private function updateApps($apps)
    {
        //禁用全部应用的 module,controller,action,permission
        Cm::$db->update('sys_auth_modules', ['deprecated' => 1], "sys_id={$this->sysID}");
        Cm::$db->update('sys_auth_module_controllers', ['deprecated' => 1], "sys_id={$this->sysID}");
        Cm::$db->update('sys_auth_module_controller_actions', ['deprecated' => 1], "sys_id={$this->sysID}");
        Cm::$db->update('sys_auth_module_controller_action_permissions', ['deprecated' => 1], "sys_id={$this->sysID} and type!=3");

        foreach ($apps as $app) {

            $moduleInfo = Cm::$db->one("SELECT id,md5 FROM sys_auth_modules WHERE sys_id={$this->sysID} AND alias='{$app['alias']}'");
            $moduleMd5 = isset($moduleInfo['md5']) ? $moduleInfo['md5'] : '';
            $moduleID = isset($moduleInfo['id']) ? $moduleInfo['id'] : 0;

            $mData = [
                'sys_id' => $this->sysID,
                'alias' => $app['alias'],
                'md5' => $app['md5'],
                'namespace' => $app['namespace'],
                'name' => $app['name'],
                'remark' => $app['remark'],
                'sort' => $app['sort'],
                'deprecated' => 0
            ];

            if ($moduleID)
                Cm::$db->update("sys_auth_modules", $mData, "id={$moduleID}");
            else {
                $mData['addtime'] = date('Y-m-d H:i:s');
                $moduleID = Cm::$db->insert("sys_auth_modules", $mData);
            }

            if (!$moduleID)
                Cm::$res->error("应用不存在");

            $app['id'] = $moduleID;

            $this->updateControllers($app, $app['controllers']);
        }
    }

    private function updateControllers($app, $controllers)
    {
        $moduleID = $app['id'];
        foreach ($controllers as $c) {

            $cInfo = Cm::$db->one("SELECT id,md5 FROM sys_auth_module_controllers WHERE sys_id={$this->sysID} AND module_id={$moduleID} AND alias='{$c['alias']}'");
            $cMd5 = isset($cInfo['md5']) ? $cInfo['md5'] : '';
            $cID = isset($cInfo['id']) ? $cInfo['id'] : 0;

            $cData = [
                'sys_id' => $this->sysID,
                'module_id' => $moduleID,
                'alias' => $c['alias'],
                'md5' => $c['md5'],
                'namespace' => $c['namespace'],
                'name' => $c['name'],
                'remark' => $c['remark'],
                'sort' => $c['sort'],
                'deprecated' => 0,
            ];

            if ($cID)
                Cm::$db->update("sys_auth_module_controllers", $cData, "id={$cID}");
            else {
                $cData['addtime'] = date('Y-m-d H:i:s');
                $cID = Cm::$db->insert("sys_auth_module_controllers", $cData);
            }

            if (!$cID)
                Cm::$res->error("应用不存在");

            $c['id'] = $cID;

            $this->updateActions($app, $c, $c['actions']);

        }
    }

    private function updateActions($app, $controller, $actions)
    {
        $moduleID = $app['id'];
        $controllerID = $controller['id'];

        foreach ($actions as $a) {

            $aInfo = Cm::$db->one("SELECT id,md5 FROM sys_auth_module_controller_actions WHERE sys_id={$this->sysID} AND module_id={$moduleID} AND controller_id={$controllerID} AND alias='{$a['alias']}'");
            $aMd5 = isset($aInfo['md5']) ? $aInfo['md5'] : '';
            $aID = isset($aInfo['id']) ? $aInfo['id'] : 0;

            $permissions = $a['permissions'];

            $route = "/{$app['alias']}/{$controller['alias']}/{$a['alias']}";

            $aData = [
                'sys_id' => $this->sysID,
                'module_id' => $moduleID,
                'controller_id' => $controllerID,
                'name' => $a['name'],
                'alias' => $a['alias'],
                'type' => $a['type'],
                'md5' => $a['md5'],
                'guest' => $permissions['guest'] ? 1 : 0,
                'common' => $permissions['common'],
                'route' => $route,
                'deprecated' => 0,
                'sort' => $a['sort'],
                'remark' => $a['remark'],
            ];

            if ($aID)
                Cm::$db->update("sys_auth_module_controller_actions", $aData, "id={$aID}");
            else {
                $aData['addtime'] = date('Y-m-d H:i:s');
                $aID = Cm::$db->insert("sys_auth_module_controller_actions", $aData);
            }

            if (!$aID)
                Cm::$res->error("action 不存在");

            $a['id'] = $aID;
            $this->updatePermissions($app, $controller, $a);
        }
    }

    private function updatePermissions($app, $controller, $action)
    {
        $moduleID = $app['id'];
        $controllerID = $controller['id'];
        $actionID = $action['id'];

        $permissions = $action['permissions'];
        $oldAllPerm = Cm::$db->all("SELECT * FROM sys_auth_module_controller_action_permissions WHERE sys_id={$this->sysID} AND action_id={$actionID}");

        if ($permissions['guest'] || $permissions['common'] == 'all')
            return;

        //页面访问权限
        if (in_array($action['type'], ['page', 'layer']) && !$permissions['access']) {
            $old = null;
            foreach ($oldAllPerm as $o) {
                if ($o['req_method'] == 'get' && !$o['type'] && $o['name'] == '页面访问')
                    $old = $o;
            }
            if (isset($old['id'])) {
                Cm::$db->update("sys_auth_module_controller_action_permissions", [
                    'deprecated' => 0
                ], "id={$old['id']}");
            } else
                Cm::$db->insert("sys_auth_module_controller_action_permissions", [
                    'sys_id' => $this->sysID,
                    'module_id' => $moduleID,
                    'controller_id' => $controllerID,
                    'action_id' => $actionID,
                    'name' => "页面访问",
                    'type' => 0,
                    'req_method' => 'get',
                    'req_key' => "",
                    'req_value' => "",
                    'deprecated' => 0,
                    'addtime' => date('Y-m-d H:i:s'),
                ]);
        }

        foreach ($permissions['get'] as $g) {
            $old = null;
            foreach ($oldAllPerm as $o) {
                if ($o['req_method'] == 'get' && $o['req_key'] == $g['key'] && $o['req_value'] == $g['value'])
                    $old = $o;
            }
            if (isset($old['id'])) {
                Cm::$db->update("sys_auth_module_controller_action_permissions", [
                    'deprecated' => 0,
                    'name' => $g['title']
                ], "id={$old['id']}");
            } else
                Cm::$db->insert("sys_auth_module_controller_action_permissions", [
                    'sys_id' => $this->sysID,
                    'module_id' => $moduleID,
                    'controller_id' => $controllerID,
                    'action_id' => $actionID,
                    'name' => $g['title'],
                    'type' => 1,
                    'req_method' => 'get',
                    'req_key' => $g['key'],
                    'req_value' => $g['value'],
                    'deprecated' => 0,
                    'addtime' => date('Y-m-d H:i:s'),
                ]);
        }

        foreach ($permissions['post'] as $g) {
            $old = null;
            foreach ($oldAllPerm as $o) {
                if ($o['req_method'] == 'post' && $o['req_key'] == $g['key'] && $o['req_value'] == $g['value'])
                    $old = $o;
            }
            if (isset($old['id'])) {
                Cm::$db->update("sys_auth_module_controller_action_permissions", [
                    'deprecated' => 0,
                    'name' => $g['title']
                ], "id={$old['id']}");
            } else
                Cm::$db->insert("sys_auth_module_controller_action_permissions", [
                    'sys_id' => $this->sysID,
                    'module_id' => $moduleID,
                    'controller_id' => $controllerID,
                    'action_id' => $actionID,
                    'name' => $g['title'],
                    'type' => 1,
                    'req_method' => 'post',
                    'req_key' => $g['key'],
                    'req_value' => $g['value'],
                    'deprecated' => 0,
                    'addtime' => date('Y-m-d H:i:s'),
                ]);
        }

    }

    //******************************************************************************************************************
    private function getStrByReg($doc, $reg, $str = '')
    {
        preg_match("/\@{$reg} .+/", $doc, $strArr);
        if (count($strArr) > 0) {
            $str = ltrim($strArr[0], "\@{$reg} ");
        }
        return $str;
    }

    private function getArrByReg($doc, $reg)
    {
        $pArr = array();
        preg_match_all("/\@{$reg} .+/", $doc, $strArr);
        if (count($strArr) > 0) {
            foreach ($strArr[0] as $item) {
                $str = ltrim($item, "\@{$reg}");
                $str = trim($str);

                $pm = array('title' => '', "key" => "", "value" => "", 'common' => false);
                $arr = explode(',', $str);
                foreach ($arr as $n) {
                    $_arr = explode(':', $n);
                    $k = isset($_arr[0]) ? $_arr[0] : '';
                    $v = isset($_arr[1]) ? $_arr[1] : '';

                    if ($k == 't')
                        $pm['title'] = $v;
                    if ($k == 'k')
                        $pm['key'] = $v;
                    if ($k == 'v')
                        $pm['value'] = str_replace('，', ',', $v);
                }
                $pArr[] = $pm;
            }
        }
        return $pArr;
    }

    private function getPostRemark($doc, $reg)
    {
        $pArr = [];
        preg_match_all("/\@{$reg}.+/", $doc, $strArr);

        if (count($strArr) > 0) {
            foreach ($strArr[0] as $item) {

                preg_match("/\@{$reg}\-[a-z0-9\_\-]{1,}/", $item, $keys);

                if (count($keys) > 0) {

                    $keys = ltrim($keys[0], "\@{$reg}");
                    $keys = trim($keys);

                    $str = ltrim($item, "\@{$reg}-{$keys}");
                    $str = trim($str);

                    $type = 'string';
                    if (json_decode($str, true))
                        $type = 'json';

                    $pArr[$keys] = ['type' => $type, 'data' => $str];
                }


            }
        }
        return $pArr;
    }

    private function getStrBoolByReg($doc, $reg)
    {
        $bool = false;
        preg_match("/\@{$reg}/", $doc, $strArr);
        if (count($strArr) > 0) {
            $bool = true;
        }
        return $bool;
    }

    //******************************************************************************************************************
    private function checkDocPermissions()
    {
        //-1：模块停用；-2：操作停用；-3：角色权限验证；0：未登录；1：通用权限通过；2：角色权限通过;3:没有权限限制
        $res = ["code" => 0, "msg" => ''];

        $namespace = str_replace("controller", 'interface', strtolower(Yii::$app->controller->className()));

        $controllerInfo = ['namespace' => $namespace];
        $this->getActions($controllerInfo);

        if ($controllerInfo['deprecated']) {
            $res['code'] = -1;
            $res['msg'] = '模块已停用';
            return $res;
        }
        $action = strtolower(Yii::$app->controller->action->id);
        foreach ($controllerInfo['actions'] as $a) {
            if ($a['alias'] == $action) {
                if ($a['deprecated']) {
                    $res['code'] = -2;
                    $res['msg'] = '操作已停用';
                    return $res;
                }
                if ($a['permissions']['guest']) {
                    $res['code'] = 3;
                    return $res;
                }
                if (Cm::$app->user->isGuest()) {
                    $res['code'] = 0;
                    $res['msg'] = '未登录';
                    return $res;
                }

                if ($a['permissions']['common'] == 'get' && Yii::$app->request->method == 'GET') {
                    $res['code'] = 1;
                    return $res;
                }

                if ($a['permissions']['common'] == 'post' && Yii::$app->request->method == 'POST') {
                    $res['code'] = 1;
                    return $res;
                }

                if ($a['permissions']['common'] == "all") {
                    $res['code'] = 1;
                    return $res;
                }
                foreach ($a['permissions']['post'] as $p) {
                    if ($p['common']) {
                        $v = isset($_GET[$p['key']]) ? $_GET[$p['key']] : '';
                        if ($v == $p['value']) {
                            $res['code'] = 1;
                            return $res;
                        }
                    }
                }

                if ($a['permissions']['access']) {
                    $hasReqLimit = false;
                    foreach ($a['permissions']['get'] as $pg) {
                        $v = isset($_GET[$pg['key']]) ? $_GET[$pg['key']] : '';
                        if ($v == $pg['value']) {
                            $hasReqLimit = true;
                        }
                    }
                    foreach ($a['permissions']['post'] as $pp) {
                        $v = isset($_GET[$pp['key']]) ? $_GET[$pp['key']] : '';
                        if ($v == $pp['value']) {
                            $hasReqLimit = true;
                        }
                    }
                    if (!$hasReqLimit) {
                        $res['code'] = 1;
                        return $res;
                    }
                }

                $res['code'] = -3;
                return $res;
            }
        }

        $res['code'] = -4;
        return $res;
    }

    private function checkRolePermissions($accountID)
    {
        $accountInfo = Cm::$db->one("SELECT is_superman,type FROM sys_manager WHERE id={$accountID}");
        $isSysManager = isset($accountInfo['is_superman']) ? $accountInfo['is_superman'] : 0;
        if ($isSysManager)
            return true;

        $route = strtolower(Yii::$app->controller->route);
        if ($route[0] != '/')
            $route = "/" . $route;

        $roleWhere = "where a.role_id in (SELECT role_id from sys_manager_role where manager_id={$accountID})";

        $sql = "SELECT b.* 
                FROM sys_auth_role_permissions a
                INNER JOIN (
                    SELECT a.id,a.type,a.req_method,a.req_key,a.req_value 
                    FROM sys_auth_module_controller_action_permissions a 
                    INNER JOIN sys_auth_module_controller_actions b ON a.action_id=b.id
                    WHERE a.module_id<>0 AND a.`status`=1 AND a.deprecated=0 AND b.`status`=1 AND b.deprecated=0 AND b.route='{$route}'
                ) b ON a.permissions_id=b.id {$roleWhere}";

        $curAllPermissions = Cm::$db->all($sql);

        $flag = false;

        if (Yii::$app->request->method == "GET") {
            $routeKey = '';
            foreach ($curAllPermissions as $per) {
                if ($per['req_method'] == 'getall')
                    return true;
                if (!empty($per['req_key']) && $per['req_method'] == 'get') {
                    $routeKey = $per['req_key'];
                }
                if ($per['type'] === '0')
                    $flag = true;
            }

            if (!empty($routeKey)) {
                if (isset($_GET[$routeKey])) {
                    $flag = false;
                    $curKeyValue = $_GET[$routeKey];
                    foreach ($curAllPermissions as $per) {
                        if ($per['req_key'] == $routeKey && $per['req_value'] == $curKeyValue) {
                            $flag = true;
                        }
                    }
                }
            }
        }

        if (Yii::$app->request->method == "POST") {
            $routeKey = '';
            foreach ($curAllPermissions as $per) {
                if ($per['req_method'] == 'postall')
                    return true;
                if (!empty($per['req_key']) && $per['req_method'] == 'post') {
                    $routeKey = $per['req_key'];
                }
            }

            if (!empty($routeKey)) {
                if (isset($_GET[$routeKey])) {
                    $flag = false;
                    $curKeyValue = $_GET[$routeKey];
                    foreach ($curAllPermissions as $per) {
                        if ($per['req_key'] == $routeKey && $per['req_value'] == $curKeyValue) {
                            $flag = true;
                        }
                    }
                }
            }
        }

        return $flag;
    }
    //******************************************************************************************************************

}